/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.ListBox
{
  public partial class PanelBase
  {
    internal class PanelSelectionAndFocusManager
    {
      #region Constructor

      public PanelSelectionAndFocusManager( PanelBase parentPanel )
      {
        m_panel = parentPanel;
      }

      #endregion

      #region Internal Methods

      internal void SetSelectedDataPath( DataPath dataPath )
      {
        m_focusOrSelectDataPath = dataPath;
      }

      internal bool HasSelectedDataPath()
      {
        return ( m_focusOrSelectDataPath != null );
      }

      internal bool IsSelectedDataPath( DataPath dataPath )
      {
        return object.Equals( m_focusOrSelectDataPath, dataPath );
      }

      internal void SetSelectedDataPathNavigationInfo( NavigationInfo navigationInfo )
      {
        m_focusOrSelectDataPathNavigationInfo = navigationInfo;
      }

      internal void ClearDataPathToSelectAndFocus()
      {
        m_focusOrSelectDataPath = null;
        m_focusOrSelectDataPathNavigationInfo = null;
      }

      // This method will return the focused listbox container in the panel.
      // If no listbox container is focused, the method returns null.
      internal ListBoxContainer FindFocusedContainer()
      {
        // Get the focus element from the focus manager and try to cast it as a ListBoxContainer
        ListBoxContainer focusedContainer = FocusManager.GetFocusedElement() as ListBoxContainer;

        // Make sure the focused container is a member of the parent listbox
        if( focusedContainer != null )
        {
          if( focusedContainer.ParentListBox != m_panel.ParentListBox )
            return null;
        }

        return focusedContainer;
      }

      internal DataPath GetFocusedOrCurrentDataPath()
      {
        // Use the FocusedContainer's DataPath for navigation as default
        ListBoxContainer focusedContainer = this.FindFocusedContainer();

        if( focusedContainer != null )
          return focusedContainer.DataPath;

        // Unable to find a focused container, use the CurrentDataPath
        return m_panel.ParentListBox.CurrentDataPath;
      }

      internal void BeginSelectAndFocusDataPath()
      {
        if( m_focusOrSelectContainerOperation == null )
        {
          m_focusOrSelectContainerOperation = m_panel.Dispatcher.BeginInvoke( this.SelectAndFocusDataPath );
        }
      }

      internal void SelectAndFocusDataPath()
      {
        m_focusOrSelectContainerOperation = null;

        DataPath dataPath = m_focusOrSelectDataPath;

        // The target container cannot be focused until its DataPath is loaded.
        if( ( dataPath != null ) && ( !dataPath.LastChild.IsLoaded ) )
          return;

        NavigationInfo navigationInfo = m_focusOrSelectDataPathNavigationInfo;

        this.ClearDataPathToSelectAndFocus();

        if( dataPath != null )
        {
          ListBoxContainer container = m_panel.LayoutManager.GetContainerFromPath( dataPath, true );

          if( container != null )
          {
            bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;

            // Update the selection.
            if( ( isShiftDown ) || ( navigationInfo != null && !navigationInfo.IsCtrlDown ) )
            {
              m_panel.ParentListBox.UpdateSelection( container, SelectionAction.Navigation );
            }

            container.Focus();
            m_panel.ProcessRecyclingWaitingList();
          }
        }
      }

      internal void BeginFocusOrSelectContainerAfterScrollForcedForNavigation( NavigationInfo navigationInfo )
      {
        if( m_focusOrSelectContainerAfterScrollForcedForNavigationOperation == null )
        {
          m_focusOrSelectContainerAfterScrollForcedForNavigationOperation =
            m_panel.Dispatcher.BeginInvoke(
              new Action<NavigationInfo>( this.FocusOrSelectContainerAfterScrollForcedForNavigation ),
              navigationInfo );
        }
      }

      internal void FocusOrSelectContainerAfterScrollForcedForNavigation( NavigationInfo navigationInfo )
      {
        m_focusOrSelectContainerAfterScrollForcedForNavigationOperation = null;

        // NavigationInfo can be null when this call is issued from BringItemIntoView
        if( navigationInfo == null )
          return;

        if( navigationInfo.ForcedBringIntoView )
        {
          DataPath focusedOrCurrentDataPath = this.GetFocusedOrCurrentDataPath();

          bool selectAndFocusOrScrollToDataPathDone =
            m_panel.SelectAndFocusOrScrollToDataPathAccordingToNavigationInfo( focusedOrCurrentDataPath, navigationInfo );

          if( !selectAndFocusOrScrollToDataPathDone )
            Debug.Assert( false, "Unable to process the keyboard navigation after the current Item was brought into view" );

          return;
        }

        //Get the path to set as current
        DataPath newCurrentOrFocusedDataPath = null;

        // We scrolled to force the last or first container to become
        // into view, get it according to scrolling direction
        newCurrentOrFocusedDataPath = ( navigationInfo.IsForwardScrolling )
          ? m_panel.LayoutManager.GetLastFullyVisibleDataPath()
          : m_panel.LayoutManager.GetFirstFullyVisibleDataPath();

        // We scrolled, but still no fully visible container
        if( newCurrentOrFocusedDataPath == null )
        {
          // Get the last
          int containerDescriptionCount = m_panel.LayoutManager.ContainerDescriptions.Count;

          if( containerDescriptionCount == 0 )
          {
            Debug.Assert( false, "We scrolled to force a container to become visible, but we didn't find any item when scrolling completed" );
            return;
          }

          // Get the first or last data path in m_containerDescriptions
          if( navigationInfo.IsForwardScrolling )
          {
            newCurrentOrFocusedDataPath = m_panel.LayoutManager.ContainerDescriptions[ containerDescriptionCount - 1 ].DataPath;
          }
          else
          {
            newCurrentOrFocusedDataPath = m_panel.LayoutManager.ContainerDescriptions[ 0 ].DataPath;
          }

          Debug.Assert( newCurrentOrFocusedDataPath != null, "We scrolled to force a container to become visible, but we didn't find any item when scrolling completed" );
        }

        if( newCurrentOrFocusedDataPath.LastChild.IsLoaded )
        {
          this.ClearDataPathToSelectAndFocus();

          ListBoxContainer container = m_panel.LayoutManager.GetContainerFromPath( newCurrentOrFocusedDataPath, true );

          if( container != null )
          {
            ListBox listBox = m_panel.ParentListBox;

            bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;
            bool isCtrlDown = navigationInfo.IsCtrlDown;

            if( !isCtrlDown || ( isCtrlDown && isShiftDown ) )
              listBox.UpdateSelection( container, SelectionAction.Navigation );

            listBox.CurrentDataPath = newCurrentOrFocusedDataPath;

            container.Focus();
            m_panel.ProcessRecyclingWaitingList();
          }
          else
          {
            Debug.Assert( false, "We scrolled so we could find the desired container" );
          }
        }
        else
        {
          // Force this method to be dispatched to be sure to call it after the scroll operation is completed
          m_focusOrSelectContainerAfterScrollForcedForNavigationOperation =
            m_panel.Dispatcher.BeginInvoke(
              new Action<NavigationInfo>( this.FocusOrSelectContainerAfterScrollForcedForNavigation ),
              navigationInfo );
        }
      }

      #endregion

      #region Private Field

      private PanelBase m_panel;

      private DispatcherOperation m_focusOrSelectContainerAfterScrollForcedForNavigationOperation;
      private DispatcherOperation m_focusOrSelectContainerOperation;
      private DataPath m_focusOrSelectDataPath;
      private NavigationInfo m_focusOrSelectDataPathNavigationInfo;

      #endregion
    }
  }
}
